package com.lagou.edu.utils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 反射工具类
 * 
 * @author ws 2020年5月12日 上午4:55:24
 * @version 1.0
 */
public class ReflectionsUtils {
    private static Log logger = LogFactory.getLog(PropertiesUtil.class);
    
    private static final String REFLECTIONS_FILE_NAME = "reflections.properties";
    
    /**
     * 获取指定包名
     */
    private static String packageName = PropertiesUtil.getPropertyValue(REFLECTIONS_FILE_NAME, "package.name");
    
    private ReflectionsUtils()
    {
        
    }
    
    /**
     * 扫包，从而获取指定的信息
     */
    private static final ReflectionsUtils utils = new ReflectionsUtils();
  //默认使用的类加载器  
    private ClassLoader classLoader = ReflectionsUtils.class.getClassLoader();
    
    /**
     * 记录对应包下的所有类名
     */
    private static final Set<String> classNamees = new HashSet<>();
    
    /**
     * 记录对应包下的所有Class信息
     */
    private static final Set<Class<?>> classes = new HashSet<>();
    
    /**
     * 从包package中获取所有的Class
     *
     * @param packageName
     * @return
     */
    Set<Class<?>> getClasses(String packageName) throws Exception
    {
        if (classes.isEmpty())
        {
            // 是否循环迭代
            boolean recursive = true;
            // 获取包的名字 并进行替换
            String packageDirName = packageName.replace('.', '/');
            // 定义一个枚举的集合 并进行循环来处理这个目录下的things
            Enumeration<URL> dirs;
            try
            {
                dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
                // 循环迭代下去
                while (dirs.hasMoreElements())
                {
                    // 获取下一个元素
                    URL url = dirs.nextElement();
                    // 得到协议的名称
                    String protocol = url.getProtocol();
                    // 如果是以文件的形式保存在服务器上
                    if ("file".equals(protocol))
                    {
                        // 获取包的物理路径
                        String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                        // 以文件的方式扫描整个包下的文件 并添加到集合中
                        addClass(classes, filePath, packageName);
                    }
                    else if ("jar".equals(protocol))
                    {
                        // 如果是jar包文件
                        // 定义一个JarFile
                        JarFile jar;
                        try
                        {
                            // 获取jar
                            jar = ((JarURLConnection)url.openConnection()).getJarFile();
                            // 从此jar包 得到一个枚举类
                            Enumeration<JarEntry> entries = jar.entries();
                            // 同样的进行循环迭代
                            while (entries.hasMoreElements())
                            {
                                // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                                JarEntry entry = entries.nextElement();
                                String name = entry.getName();
                                // 如果是以/开头的
                                if (name.charAt(0) == '/')
                                {
                                    // 获取后面的字符串
                                    name = name.substring(1);
                                }
                                // 如果前半部分和定义的包名相同
                                if (name.startsWith(packageDirName))
                                {
                                    int idx = name.lastIndexOf('/');
                                    // 如果以"/"结尾 是一个包
                                    if (idx != -1)
                                    {
                                        // 获取包名 把"/"替换成"."
                                        packageName = name.substring(0, idx).replace('/', '.');
                                    }
                                    // 如果可以迭代下去 并且是一个包
                                    if ((idx != -1) || recursive)
                                    {
                                        // 如果是一个.class文件 而且不是目录
                                        if (name.endsWith(".class") && !entry.isDirectory())
                                        {
                                            // 去掉后面的".class" 获取真正的类名
                                            String className = name.substring(packageName.length() + 1, name.length() - 6);
                                            try
                                            {
                                                // 添加到classes
                                                classes.add(Class.forName(packageName + '.' + className));
                                            }
                                            catch (ClassNotFoundException e)
                                            {
                                                e.printStackTrace();
                                            }
                                        }
                                    }
                                }
                            }
                            

                        }
                        catch (IOException e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
                
                for (String classsName : classNamees)
                {
                    doAddClass(classsName);
                }
                
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
        
        return classes;
    }
    
    void addClass(Set<Class<?>> classes, String filePath, String packageName) throws Exception
    {
        File[] files = new File(filePath).listFiles();
        if (files != null)
        {
            for (File file : files)
            {
                if (file.isFile() && file.getName().endsWith(".class"))
                {
                    String fileName = file.getName();
                    String classsName = fileName.substring(0, fileName.lastIndexOf("."));
                    if (!packageName.isEmpty())
                    {
                        classsName = packageName + "." + classsName;
                    }
                    classNamees.add(classsName);
                }
                else
                {
                    addClass(classes, file.getPath(), packageName + "." + file.getName());
                }
            }
        }
    }
    
    void doAddClass(final String classsName) throws Exception
    {
        try
        {
            classes.add(classLoader.loadClass(classsName));
        }
        catch (Exception e)
        {
            classes.add(Class.forName(classsName));
        }
        
    }
    
    <A extends Annotation> Set<Class<?>> getTypesAnnotatedWith(String packageName, Class<A> annotationClass) throws Exception
    {
        
        // 找用了annotationClass注解的类
        Set<Class<?>> controllers = new HashSet<>();
        Set<Class<?>> clsList = getClasses(packageName);
        if (clsList != null && clsList.size() > 0)
        {
            for (Class<?> cls : clsList)
            {
                if (cls.getAnnotation(annotationClass) != null)
                {
                    controllers.add(cls);
                }
            }
        }
        return controllers;
    }
    
    /**
     * 根据给定的注解信息获取对应的class信心
     * 
     * @param annotation
     *            注解类
     * @return
     * @author ws 2020年5月12日 上午5:56:41
     * @version 1.0
     */
    public static Set<Class<?>> getClassesByAnnotaion(final Class<? extends Annotation> annotation)
    {
        Set<Class<?>> classSet = new HashSet<>();
        try
        {
            classSet = utils.getTypesAnnotatedWith(packageName, annotation);
        }
        catch (Exception e)
        {
            logger.error("扫包失败：", e);
        }
        return classSet;
    }
    
    /**
     * 根据给定的类和注解信息获取对应的字段
     * 
     * @param clazz
     *            目标类
     * @param annotation
     *            指定注解
     * @return
     * @author ws 2020年5月12日 下午3:51:39
     * @version 1.0
     */
    public static <T> Set<Field> getFieldsAnnotatedWith(final Class<T> clazz, final Class<? extends Annotation> annotation)
    {
        Set<Field> fieldSet = new HashSet<>();
        for (Field fieldTemp : clazz.getDeclaredFields())
        {
            if (fieldTemp.getAnnotation(annotation) != null)
            {
                fieldSet.add(fieldTemp);
            }
        }
        return fieldSet;
        
    }
    
    /**
     * 根据给定的对象和字段赋值，
     * 
     * @param obj
     *            对象
     * @param fieldName
     *            字段
     * @param val
     *            值
     * @throws NoSuchFieldException
     * @throws SecurityException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @author ws 2020年5月12日 下午10:12:29
     * @version 1.0
     */
    public static void setValue(Object obj, String fieldName, Object val) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException
    {
        Field f = obj.getClass().getDeclaredField(fieldName);
        f.setAccessible(true);
        f.set(obj, val);
    }
    
}
